package com.weilus.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.r2dbc.core.DatabaseClient;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Update;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import reactor.core.publisher.Mono;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class AbstractMapper<T> {

    private static Pattern humpPattern = Pattern.compile("[A-Z]");
    @Autowired
    protected DatabaseClient databaseClient;

    public Mono<Integer> updateByPrimaryKeySelective(T t){
        return updateByCriteriaSelective(t,getPrimaryKeyCriteria(t));
    }

    public Mono<Integer> updateByCriteriaSelective(T t,Criteria criteria){
        return databaseClient.update().table(getTable(t))
                .using(updateSelective(t))
                .matching(criteria)
                .fetch()
                .rowsUpdated();
    }

    private String getTable(T t){
        Table table = t.getClass().getDeclaredAnnotation(Table.class);
        return table != null ? table.value() : humpToLine(t.getClass().getName());
    }

    private Criteria getPrimaryKeyCriteria(T t){
        Optional<Field> op = Stream.of(t.getClass().getDeclaredFields())
                .filter(field -> field.getDeclaredAnnotation(Id.class) != null).findFirst();
        if(op.isPresent()){
            Field id = op.get();
            id.setAccessible(true);
            try {
                return Criteria.where(humpToLine(id.getName())).is(id.get(t));
            } catch (IllegalAccessException e) {
                throw new RuntimeException("获取主键失败",e);
            }
        } else {
            throw new RuntimeException(t.getClass().getName() + "未声明主键: @ID");
        }
    }

    private Update updateSelective(T t){
        Map<SqlIdentifier, Object> assignments = new HashMap<>();
        for(Field field:t.getClass().getDeclaredFields()){
            field.setAccessible(true);
            Object v = null;
            try {
                v = field.get(t);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            boolean isId = field.getDeclaredAnnotation(Id.class) != null;
            if(v != null && !isId){
                String column = humpToLine(field.getName());
                assignments.put(SqlIdentifier.unquoted(column),v);
            }
        }
        return Update.from(assignments);
    }

    public static String humpToLine(String str) {
        Matcher matcher = humpPattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
}
